package com.seleniti.enpareja.android.control.controller;

import org.ioc.commons.flowcontrol.common.impl.ReceiverAdapter;
import com.seleniti.enpareja.controller.control.controller.LoginFailedCause;
import org.ioc.commons.flowcontrol.operationmanager.IsOperation;
import org.ioc.commons.flowcontrol.operationmanager.OperationManager;
import org.ioc.commons.flowcontrol.placecontroller.HasPlaceData;
import org.ioc.commons.impl.android.flowcontrol.logincontroller.AndroidLoginControllerImpl;
import org.ioc.commons.integration.common.RequestManager;
import org.ioc.commons.model.globaldata.GlobalDataController;
import org.ioc.commons.utils.FormatterLogger;

import android.accounts.AccountManager;
import android.os.Bundle;
import android.view.HasContext;

import com.seleniti.enpareja.controller.common.GlobalData;
import com.seleniti.enpareja.controller.common.LogInType;
import com.seleniti.enpareja.controller.common.domain.LoggedUserInfo;
import com.seleniti.enpareja.controller.common.domain.UserAgent;
import com.seleniti.enpareja.controller.control.controller.LoginParams;
import com.seleniti.enpareja.controller.control.place.EnParejaPlace;
import com.seleniti.enpareja.controller.model.integration.service.SecurityManagementService;

public class EnParejaLoginController extends
		AndroidLoginControllerImpl<LoggedUserInfo, LoginFailedCause, LoginParams, String> {

	private static final FormatterLogger logger = FormatterLogger.getLogger(EnParejaLoginController.class);

	private SecurityManagementService service;
	private GlobalDataController<GlobalData> globalDataController;

	public EnParejaLoginController(HasContext loginControllerContext, SecurityManagementService service,
			GlobalDataController<GlobalData> globalDataController) {
		super(loginControllerContext);
		this.service = service;
		this.globalDataController = globalDataController;
	}

	@Override
	protected <O extends Enum<? extends IsOperation>> void doLogin(
			final String loginName,
			final String loginPassword,
			final LoginParams loginParams,
			final org.ioc.commons.flowcontrol.logincontroller.LoginController.LoginResponse<LoggedUserInfo, LoginFailedCause> loginResponse,
			O operation, OperationManager<O> operationManager) {

		final LogInType type = loginParams.getLoginType();
		final EnParejaPlace whenLoginOk = loginParams.getWhenLoginOk();

		// placeController = placeControllerProvider.forThisObject(this);

		switch (type) {
		case GAE:
			UserAgent ua = (UserAgent) this.globalDataController.getGlobalDataValue(GlobalData.UserAgent);

			if (ua == UserAgent.ANDROID_APP) {
				placeController.goTo(EnParejaPlace.GoogleLoginPlace);
			} else {
				HasPlaceData place = null;
				if (whenLoginOk != null) {
					place = this.placeController.getPlaceData(whenLoginOk);
				} else {
					place = this.placeController.getFirstPlace();
				}
				RequestManager<String> rm = this.service
						.getGoogleLoginExternalUrl(this.placeController.getPlaceAsUrl(place)).caller(this)
						.to(new ReceiverAdapter<String>() {

							@Override
							public void success(String externalLoginUrl) {
								placeController.goTo(EnParejaPlace.ExternalLoginPlace, false, null, externalLoginUrl);
							}
						});

				if (operation != null && operationManager != null) {
					rm.performing(operation, operationManager).caller(this);
				}

			}
			break;
		case UserPassword:

			logger.info("Try login '{0}'...", loginName);

			RequestManager<LoggedUserInfo> rm = this.service.tryLogin(loginName, loginPassword).with(/*
																									 * No
																									 * need
																									 */).caller(this)
					.to(new ReceiverAdapter<LoggedUserInfo>() {

						@Override
						public void success(LoggedUserInfo result) {
							logger.info("... login tried.");
							checkDoUserPasswordLoginResult(result, loginName, loginPassword, type, whenLoginOk, loginResponse);
						}

					});
			if (operation != null && operationManager != null) {
				rm.performing(operation, operationManager).caller(this);
			}
			break;
		}
	}

	protected void checkDoUserPasswordLoginResult(
			LoggedUserInfo loggedUserInfo,
			String loginName,
			String loginPassword,
			LogInType loginType,
			EnParejaPlace goWhenLoginOk,
			org.ioc.commons.flowcontrol.logincontroller.LoginController.LoginResponse<LoggedUserInfo, LoginFailedCause> loginResponse) {
		/**
		 * Set the received login info in the global context
		 */
		this.loggedUserInfo = loggedUserInfo;

		if (!loggedUserInfo.isLogged()) {
			if (loggedUserInfo.getUserEmail()!=null && !loggedUserInfo.isPasswordSet()) {
				loginResponse.onLoginFailed(LoginFailedCause.NO_PASSWORD_SET, null);
			} else {
				loginResponse.onLoginFailed(LoginFailedCause.USER_OR_PASS_INVALID, null);
			}
		} else {

			loginResponse.onLoginOk(loggedUserInfo);

			if (this.placeController != null) {
				/**
				 * Go back from the place which required a login
				 */
				if (goWhenLoginOk == null) {
					goWhenLoginOk = EnParejaPlace.Main;
				}

				this.placeController.goBackOrTo(goWhenLoginOk);
			}
		}

	}

	@Override
	public <O extends Enum<? extends IsOperation>> void logout(
			final org.ioc.commons.flowcontrol.logincontroller.LoginController.LogoutResponse<String> logoutResponse,
			O operation, OperationManager<O> operationManager) {
		LoggedUserInfo lui = this.getLoggedUserInfo();

		if (lui != null && lui.isRequiredExternalLogoutUrl()) {
			logger.info("Login out externally");
			RequestManager<String> rm = service.getGoogleLogoutExternalUrl(this.placeController
					.getPlaceAsUrl(this.placeController.getFirstPlace()));
			if (operation != null) {
				rm.performing(operation, operationManager);
			}
			rm.caller(this);
			rm.to(new ReceiverAdapter<String>() {

				@Override
				public void success(String externalLogoutUrl) {
					if (logoutResponse != null) {
						logoutResponse.onLogout(externalLogoutUrl);
					}
				}
			});
		} else {
			logger.info("Login out internally");

			RequestManager<Void> rm = service.logout();
			if (operation != null) {
				rm.performing(operation, operationManager);
			}
			rm.caller(this);
			rm.to(new ReceiverAdapter<Void>() {

				@Override
				public void success(Void result) {
					refreshLoggedUserInfo();
					if (signInView != null) {
						placeController.goTo(signInView);
					}
					if (logoutResponse != null) {
						logoutResponse.onLogout(null);
					}
				}
			});
		}
	}

	@Override
	protected String getAuthTokenFromLoggedUserInfo(LoggedUserInfo loggedUserInfo) {
		return loggedUserInfo != null ? loggedUserInfo.getSessionId() : null;
	}

	@Override
	protected LoginParams getExtraLoginParametersFrom(String name, String password, String authTokenType) {
		return new LoginParams(LogInType.valueOf(authTokenType));
	}

	@Override
	protected String getByDefaultAuthTokenType() {
		return LogInType.UserPassword.name();
	}

	@Override
	protected Bundle checkInvalidAuthTokenType(String authTokenType) {
		try {
			LogInType.valueOf(authTokenType);
			return null;
		} catch (Exception e) {
			final Bundle result = new Bundle();
			result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType");
			return result;
		}
	}

	@Override
	public String getAuthTokenLabel(String authTokenType) {
		try {
			return LogInType.valueOf(authTokenType).getLabel();
		} catch (Exception e) {
			return authTokenType;
		}
	}

	@Override
	protected String getAccountType() {
		return "com.seleniti.enpareja";
	}

	@Override
	public boolean isLogged() {
		return this.loggedUserInfo != null && this.loggedUserInfo.isLogged();
	}

}
